#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <sstream>

cv::Mat gamma_correction_1(const cv::Mat& img, double gamma) {
    CV_Assert(img.type() == CV_8UC3);
    cv::Mat res(img.rows, img.cols, img.type());
    for (int i = 0; i < img.rows; i++) {
        const cv::Vec3b* p = img.ptr<cv::Vec3b>(i);
        cv::Vec3b* pp = res.ptr<cv::Vec3b>(i);
        for (int j = 0; j < img.cols; j++) {
            pp[j][0] = cv::saturate_cast<uchar>(cv::pow(p[j][0] / 255.0, gamma) * 255.0);
            pp[j][1] = cv::saturate_cast<uchar>(cv::pow(p[j][1] / 255.0, gamma) * 255.0);
            pp[j][2] = cv::saturate_cast<uchar>(cv::pow(p[j][2] / 255.0, gamma) * 255.0);
        }
    }
    return res;
}

cv::Mat gamma_correction_2(const cv::Mat& img, double gamma) {
    CV_Assert(img.type() == CV_8UC3);
    cv::Mat lookUpTable(1, 256, CV_8UC1);
    uchar* p = lookUpTable.data;
    for (int i = 0; i < 256; i++) {
        p[i] = cv::saturate_cast<uchar>(cv::pow(i / 255.0, gamma) * 255.0);
    }
    cv::Mat res;
    cv::LUT(img, lookUpTable, res);
    return res;
}

int main() {
    /*
    cv::Mat img1(8, 8, CV_8UC1);
    cv::randu(img1, cv::Scalar::all(0), cv::Scalar::all(255));
    std::cout << "source:\n" << img1 << std::endl << std::endl;
    cv::Mat img2;
    double alpha = 0.5;
    double beta = 100;
    img1.convertTo(img2, -1, alpha, beta);
    std::cout << "convertTo:\n" << img2 << std::endl << std::endl;

    cv::Mat img3(8, 8, CV_8UC1);
    for (int i = 0; i < img1.rows; i++) {
        uchar* p = img1.ptr<uchar>(i);
        uchar* pp = img3.ptr<uchar>(i);
        for (int j = 0; j < img1.cols; j++) {
            pp[j] = cv::saturate_cast<uchar>(p[j] * alpha + beta);
        }
    }
    std::cout << "i * alpha + beta:\n" << img3 << std::endl << std::endl;

    cv::Mat diff;
    diff = img2 - img3;
    std::cout << "diff:\n" << diff << std::endl << std::endl;

    */

    /*
    cv::String imageFile = "/home/xlll/Downloads/opencv/samples/data/aero1.jpg";
    cv::Mat img = cv::imread(imageFile, cv::IMREAD_COLOR);
    if (img.empty()) {
        std::cout << "Failed to read image!" << std::endl;
        return EXIT_FAILURE;
    }

    cv::Mat res;
    double alpha = 0.5;
    double beta = 0;
    img.convertTo(res, -1, alpha, beta);
    cv::imshow("source", img);
    cv::imshow("alpha < 1", res);

    cv::Mat res1;
    alpha = 1.2;
    beta = 0;
    img.convertTo(res1, -1, alpha, beta);
    cv::imshow("alpha > 1", res1);

    cv::Mat res2;
    alpha = 1;
    beta = 100;
    img.convertTo(res2, -1, alpha, beta);
    cv::imshow("beta", res2);

    cv::Mat res3;
    alpha = 1.1;
    beta = 30;
    img.convertTo(res3, -1, alpha, beta);
    cv::imshow("alpha > 1 + beta", res3);

    cv::Mat res4;
    alpha = 0.5;
    beta = 100;
    img.convertTo(res4, -1, alpha, beta);
    cv::imshow("alpha < 1 + beta", res4);

    */

    /*
    cv::String imageFile = "/home/xlll/Downloads/opencv/samples/data/aero3.jpg";
    cv::Mat img = cv::imread(imageFile, cv::IMREAD_COLOR);
    if (img.empty()) {
        std::cout << "Failed to read image!" << std::endl;
        return EXIT_FAILURE;
    }

    cv::Mat res;
    double alpha = 1.3;
    double beta = 10;
    img.convertTo(res, -1, alpha, beta);
    cv::imshow("source", img);
    cv::imshow("res", res);

    cv::Mat res1, res2;
    double gamma = 1.2;
    res1 = gamma_correction_1(img, gamma);
    res2 = gamma_correction_2(img, gamma);
    cv::imshow("res1", res1);
    cv::imshow("res2", res2);

    */

    const int WIDTH = 600;
    const int HEIGHT = 400;
    cv::Mat mat = cv::Mat::zeros(HEIGHT, WIDTH, CV_8UC3);
    cv::Point pts[1][256];
    double gammas[] = {0.04, 0.1, 0.2, 0.3, 0.4, 0.6, 1., 1.5, 2.5, 3.5, 5, 10, 25};
    cv::Scalar colors[] = {cv::Scalar(255, 0, 0), cv::Scalar(0, 255, 0), cv::Scalar(0, 0, 255), cv::Scalar(255, 255, 0),
                           cv::Scalar(255, 0, 255), cv::Scalar(0, 128, 255), cv::Scalar(128, 0, 0), cv::Scalar(0, 128, 0),
                           cv::Scalar(0, 0, 128), cv::Scalar(128, 128, 0), cv::Scalar(128, 0, 128), cv::Scalar(0, 128, 128),
                           cv::Scalar(0, 0, 0)};
    int indices[] = {20, 40, 60, 80, 100, 120, 130, 140, 160, 180, 200, 220, 240};
    cv::rectangle(mat, cv::Rect(0, 0, WIDTH, HEIGHT), cv::Scalar(255, 255, 255), -1);
    for (int i = 0; i < 13; i++) {
        for (int j = 0; j < 256; j++) {
            int x = j * WIDTH / 256;
            int y = HEIGHT - (int)(cv::pow(j / 255.0, gammas[i]) * 255.0) * HEIGHT / 256;
            pts[0][j] = cv::Point(x, y);
        }
        const cv::Point* ppt[1] = {pts[0]};
        int npt[1] = {256};
        cv::polylines(mat, ppt, npt, 1, false, colors[i], 1, cv::LINE_AA);
        std::ostringstream stream;
        stream << gammas[i];
        cv::putText(mat, stream.str(), pts[0][indices[i]], 1, 1, colors[i]);
    }
    cv::imshow("gammas", mat);
    cv::waitKey(0);

    return EXIT_SUCCESS;
}
